# encoding: utf-8


"""

@author: tongzhenguo

@time: 2021/5/14 下午8:57

@desc:

技术面指标相关分析预测 todo  成交量指标
技术面量价关系相关分析预测
技术面均线系统相关分析预测  todo 双均线
识别多头/空头行情

相关链接
1.talib官方文档:https://mrjbq7.github.io/ta-lib/doc_index.html
"""

import pandas as pd
import talib as ta

from const import TRADE_CAL_FILE
from date_util import get_n_day, transform, now_date_fmt, gen_k_date_list, now_date_ts, last_n_year, now_year
from mongo_db import MongoDB


class MyTalib(object):
    def __init__(self):
        super().__init__()
        ma_type_list = ['SMA', 'EMA', 'WMA', 'DEMA', 'TEMA', 'TRIMA', 'KAMA', 'MAMA', 'T3']
        self.ma_type_dict = dict(zip(ma_type_list, [ix for ix, _ in enumerate(ma_type_list)]))
        self.ta_pattern_text = ['十字星', '两只乌鸦', '三只乌鸦', '三内部上涨和下跌', '三线打击',
                                '三外部上涨和下跌', '南方三星', '三个白兵', '弃婴', '大敌当前',
                                '捉腰带线', '脱离', '收盘缺影线', '藏婴吞没', '反击线',
                                '乌云压顶', '蜻蜓十字/T形十字', '吞噬模式', '十字暮星', '暮星',
                                '向上/下跳空并列阳线', '墓碑十字/倒T十字', '锤头', '上吊线', '母子线',
                                '十字孕线', '风高浪大线', '陷阱', '修正陷阱', '家鸽',
                                '三胞胎乌鸦', '颈内线', '倒锤头', '反冲形态', '由较长缺影线决定的反冲形态',
                                '停顿形态', '条形三明治', '探水竿', '跳空并列阴阳线', '插入',
                                '三星', '奇特三河床', '向上跳空的两只乌鸦', '上升/下降跳空三法']

    def history_k_pattern_buy_signal(self, df, open_cols, high_cols, low_cols, close_cols):
        """-100：出现卖出信号  0：没有出现该形态   100：出现买入信号

        相关链接：
        https://www.jianshu.com/p/fd5c7f49db33
        http://www.snailtoday.com/archives/15787
        """

        def func(row, o, func_name):
            fn = getattr(o, func_name)
            open = row[open_cols].values.astype(float)
            high = row[high_cols].values.astype(float)
            low = row[low_cols].values.astype(float)
            close = row[close_cols].values.astype(float)
            pattern_output = fn(open, high, low, close)[-1]
            return pattern_output

        def get_buy_signal(row):
            if row["红三兵"] == 100:
                return "红三兵"
            elif row["CDL3INSIDE"] == 100:
                return "CDL3INSIDE"
            elif row["早晨之星"] == 100:
                return "早晨之星"
            elif row["金针探底"] == 100:
                return "金针探底"
            else:
                return "无"

        # 早晨之星
        df["早晨之星"] = df.apply(lambda r: func(r, ta, "CDLMORNINGSTAR"), axis=1)
        # 金针探底，一日K线模式，大致与蜻蜓十字相同，下影线长度长。
        df["金针探底"] = df.apply(lambda r: func(r, ta, "CDLTAKURI"), axis=1)
        # 第三天收盘价高于第一天开盘价，第二天K线在第一天K线内部，预示着股价上涨。
        df["CDL3INSIDE"] = df.apply(lambda r: func(r, ta, "CDL3INSIDE"), axis=1)
        # 红三兵,每日收盘价变高且接近最高价，开盘价在前一日实体上半部，预示股价上升。
        df["红三兵"] = df.apply(lambda r: func(r, ta, "CDL3WHITESOLDIERS"), axis=1)
        df["买入信号"] = df.apply(get_buy_signal, axis=1)
        return df

    def ma(self, close, timeperiod, matype):
        """移动平均线一般要与其他的技术指标或基本面相结合来使用，特别是当市场处于盘整行情时，其买入卖出信号会频繁出现，容易失真。
        https://www.cnblogs.com/bilx/p/11644700.html
        """
        # FIXME 算出来的和同花顺ma数值不一致
        return close.rolling(timeperiod).mean()
        # return ta.MA(close, timeperiod=timeperiod, matype=matype)

    def boll(self, df, timeperiod, nbdevup, nbdevdn, matype):
        """布林带(Bollinger Band)，由压力线、支撑线价格平均线组成，在判断盘整行情终结节点上成功率较高。"""
        return ta.BBANDS(df.close, timeperiod=timeperiod, nbdevup=nbdevup, nbdevdn=nbdevdn, matype=matype)

    def kdj(self, df):
        """
        定义
        以KDJ(9,3,3) 为例:
        RSV(9)=（今日收盘价－9日内最低价）÷（9日内最高价－9日内最低价）×100
        K(3日)=（当日RSV值+2×前一日K值）÷3
        D(3日)=（当日K值+2×前一日D值）÷3
        J=3K－2D

        应用
        K线进入90以上为超买区, 10以下为超卖区; D线进入80以上为超买区, 20以下为超卖区。
        J指标为依据KD 买卖信号是否可以采取行动提供可信判断。
        通常，当 J值大于100或小于10 时被视为采取买卖行动的时机。

        """
        low_list = df['low'].rolling(9, min_periods=9).min()
        low_list.fillna(value=df['low'].expanding().min(), inplace=True)
        high_list = df['high'].rolling(9, min_periods=9).max()
        high_list.fillna(value=df['high'].expanding().max(), inplace=True)
        rsv = (df['close'] - low_list) / (high_list - low_list) * 100
        df['K'] = pd.DataFrame(rsv).ewm(com=2).mean()
        df['D'] = df['K'].ewm(com=2).mean()
        df['J'] = 3 * df['K'] - 2 * df['D']
        return df

    def ad(self, df):
        """用价格以及成交量来估算成交流量，从而判断买卖双方的力量对比，寻找交易机会
        定义
        AD = 前一日AD + 成交量 * CLV,回看的时间窗口，一般设为26天
        CLV = (2*Close – High - low) / (High - Low),当High=Low时，CLV值为0.

        应用
        如果A/D线上升的同时，价格也在上升，则说明上升趋势被确认，产生买入信号；
        如果A/D线下降的同时，价格也在下降，则说明下降趋势被确认，产生卖出信号；
        """
        df["ad"] = ta.AD(df.high, df.low, df.close, df.volume).apply(lambda _: round(_, 2))
        return df

    def adosc(self, df):
        """
        定义
        fastperiod A/D - slowperiod A/D

        应用
        交易信号是背离，看涨背离做多，看跌背离做空
        股价和90天移动平均线结合与其他指标结合
        由正变负卖出，由负变正买进
        """
        df["adosc"] = ta.ADOSC(df.high, df.low, df.close, df.volume, fastperiod=3, slowperiod=10).apply(
            lambda _: round(_, 2))
        return df

    def obv(self, df):
        """
        定义
        以某日为基期，逐日累计每日上市股票总成交量，若隔日指数或股票上涨，
        则基期OBV加上本日成交量为本日OBV。隔日指数或股票下跌，则基期OBV减去本日成交量为本日OBV

        复杂版：OBV=前一日OBV+当日成交量(V)×多空比例净额
        """
        df["obv"] = ta.OBV(df.close, df.volume).apply(lambda _: round(_, 2))
        return df

    def beta(self):
        """贝塔系数
        贝塔系数衡量股票收益相对于业绩评价基准收益的总体波动性，是一个相对指标。
        β 越高，意味着股票相对于业绩评价基准的波动性越大。
        β 大于 1 ， 则股票的波动性大于业绩评价基准的波动性。
        https://www.bookstack.cn/read/talib-zh/func_groups-statistic_functions.md
        """
        pass

    def var(self):
        """方差"""
        pass

    def atr(self):
        """真实波动幅度均值
        真实波动幅度 = max(最大值,昨日收盘价) − min(最小值,昨日收盘价)
        """
        pass

    @staticmethod
    def w_bottom(row, start_date, end_date):
        """
        识别w(双重)底
        fixme 小双底一个月，现在使用两个月数据
        TODO 增加回踩确认判定
        双重底：
        找区间1的极小值，为左底(低于起始5元)
        找区间2的极小值，为右底
        找左底与右底之间区域的极大值
        比较左底与右底的涨幅，是否相差<3%(右底至少和左底持平)
        当前日期收盘价是否突破颈线位>3%

        注意点：
        1.股价的变动与成交量的变动向同一方面变化
        2.在双重底形成的过程中，如果股价从第二个支撑点反弹后，出现了第三次回跌，其跌幅不得超过第二次跌幅的1/3
        3.双底不一定都是反转信号，有时也会是整理形态，如果两个低点出现时间非常近，在它们之间只有一个次级上升，大部分属于整理形态
        4.双底完成后，突破颈线幅度超过3根K线收盘价在颈线之上，是有效突破。
        5.测量双重底形最小上涨距离，至少达到波谷与波峰之间垂直距离，也就是最低价与颈线间差距，涨势才能稍止。
        fixme 警惕双重顶混淆为双重底
        todo bad case:紫光国微,

        W底(双重底)买入原则：
        1.在股价放量突破颈线位时可以买入。
        2.股价再次回抽颈线位时也可以买入。
        3.投资者在买入后，可以将止损设在颈线位，跌破颈线位后止损离场。
        4.两底形成的过程中，累计换手率越大，成交量配合的越好，为主力吸筹的表现，未来上涨的空间也越大。

        相关链接:
        http://www.jishudaxue.com/cblog/python/3748.html
        http://www.360doc.com/content/10/1217/17/2488408_79040985.shtml

        :return: str,识别结果为"双底形态判断有效","双底形态突破有效","双底形态判断无效","双底形态突破无效"中的一种
        """
        code = row['_id']
        # 创业板和科创板买不了
        if code[0] == "3" or code[:3] >= "688":
            return "双底形态判断无效"
        name = row["2020年度-股票简称"]
        total_duration = int(
            (transform(str(end_date), "str", "timestamp", src_format="YYYYMMDD") -
             transform(str(start_date), "str", "timestamp", src_format="YYYYMMDD")) / 86400)
        # freq = "d"
        min_diff = 0.03
        mid_date = get_n_day(start_date, total_duration / 2)
        bottom1_price = None
        bottom1_date = None
        bottom2_price = None
        bottom2_date = None
        neckline_price = None
        neckline_date = None
        # 区间1的最高价格
        max1_price = 0
        while start_date < mid_date:
            if not bottom1_date:
                if f"{start_date}日-收盘价" in row:
                    if row[f"{start_date}日-收盘价"] == "-1":
                        start_date = get_n_day(start_date, 1)
                        continue
                    max1_price = max(float(row[f"{start_date}日-收盘价"]), max1_price)
                    bottom1_price = row[f"{start_date}日-收盘价"]
                    bottom1_date = start_date
            elif f"{start_date}日-收盘价" in row and row[f"{start_date}日-收盘价"] < bottom1_price:
                if row[f"{start_date}日-收盘价"] == "-1":
                    start_date = get_n_day(start_date, 1)
                    continue
                bottom1_price = row[f"{start_date}日-收盘价"]
                bottom1_date = start_date
            start_date = get_n_day(start_date, 1)
        while end_date > mid_date:
            if not bottom2_date:
                if f"{mid_date}日-收盘价" in row:
                    if row[f"{mid_date}日-收盘价"] == "-1":
                        mid_date = get_n_day(mid_date, 1)
                        continue
                    bottom2_price = row[f"{mid_date}日-收盘价"]
                    bottom2_date = mid_date
            elif f"{mid_date}日-收盘价" in row and row[f"{mid_date}日-收盘价"] < bottom2_price:
                if row[f"{mid_date}日-收盘价"] == "-1":
                    mid_date = get_n_day(mid_date, 1)
                    continue
                bottom2_price = row[f"{mid_date}日-收盘价"]
                bottom2_date = mid_date
            mid_date = get_n_day(mid_date, 1)
        if not bottom1_date or not bottom2_date:
            # print(f"{name} bottom2_date is None or bottom1_date is None !")
            return "双底形态判断无效"
        elif max1_price - float(bottom1_price) < 5:
            # print(f"{name} left range decrease < 5 or not decrease !")
            return "双底形态判断无效"
        start_date = get_n_day(bottom1_date, 1)
        while start_date < bottom2_date:
            if not neckline_date:
                if row[f"{start_date}日-收盘价"] == "-1":
                    start_date = get_n_day(start_date, 1)
                    continue
                neckline_price = row[f"{start_date}日-收盘价"]
                neckline_date = start_date
            elif f"{start_date}日-收盘价" in row and row[f"{start_date}日-收盘价"] > neckline_price:
                if row[f"{start_date}日-收盘价"] == "-1":
                    start_date = get_n_day(start_date, 1)
                    continue
                neckline_price = row[f"{start_date}日-收盘价"]
                neckline_date = start_date
            start_date = get_n_day(start_date, 1)
        # NOTE 预期涨幅小于5元的没有意义，也算作非W底
        # NOTE 颈线价格要大于两个底部价格
        if not neckline_price or float(neckline_price) - float(bottom1_price) < 5 or float(neckline_price) - float(
                bottom2_price) < 0:
            # print(f"{name} neckline_price is None or Price increase < 5 !")
            return "双底形态判断无效"
        elif float(bottom2_price) < float(bottom1_price) or (float(bottom2_price) - float(bottom1_price)) / float(
                bottom1_price) < min_diff:
            # print(f"{name} bottom2_price is low")
            return "双底形态判断无效"
        now_price = float(row[f"{end_date}日-收盘价"])
        target_price = 2 * float(neckline_price) - float(bottom2_price)
        min_increase = target_price - now_price
        if f"{end_date}日-收盘价" not in row:
            print(f"error {name} {end_date}日-收盘价 not sink db !!!")
        # elif float(now_price) - float(neckline_price) < -5 or float(now_price) - float(bottom2_price) < 5:
        #     print(f"{name} It is far away from the neckline or not increase !")
        #     return "双底形态判断无效"
        if (float(now_price) - float(neckline_price)) / float(neckline_price) > min_diff:
            if float(now_price) > min_increase + float(neckline_price):
                # print(f"{name}({code}) 已突破最低涨幅完毕，跳过！！！")
                return "双底形态突破有效"
            else:
                print(
                    f"{name}({code}) 双底形态突破有效, 最低涨幅 {min_increase}, 左底 {bottom1_date}/{bottom1_price},"
                    f" 右底 {bottom2_date}/{bottom2_price}, 颈线价格 {neckline_price}, 当前收盘价 {now_price} ")
                return "双底形态突破有效"
        else:
            print(
                f"{name}({code}) 双底形态判断有效, 左底 {bottom1_date}/{bottom1_price},"
                f" 右底 {bottom2_date}/{bottom2_price}, 颈线价格 {neckline_price}, 当前收盘价 {now_price} ")
            return "双底形态判断有效"

    @staticmethod
    def m_head(row, start_date, end_date):
        """
        识别m(双重顶)头
        fixme 小双底一个月，现在使用两个月数据
        双重顶：
        找区间1的极大值，为左顶(高于起始5%)
        找区间2的极大值，为右顶
        找左顶与右顶之间区域的极小值
        比较左底与右底的涨幅，右顶至多和左顶持平
        当前日期收盘价是否跌破颈线位>3%

        注意点：
        1.股价的变动与成交量的变动向同一方面变化

        相关链接:
        http://www.jishudaxue.com/cblog/python/3748.html
        http://www.360doc.com/content/10/1217/17/2488408_79040985.shtml

        :return: str,识别结果为"双顶形态判断有效","双顶形态跌破有效","双顶形态判断无效","双顶形态跌破无效"中的一种
        """
        code = row['_id']
        # 创业板和科创板买不了
        if code[0] == "3" or code[:3] >= "688":
            return "双顶形态判断无效"
        name = row["2020年度-股票简称"]
        total_duration = int(
            (transform(str(end_date), "str", "timestamp", src_format="YYYYMMDD") -
             transform(str(start_date), "str", "timestamp", src_format="YYYYMMDD")) / 86400)
        # freq = "d"
        min_diff = 0.03
        mid_date = get_n_day(start_date, total_duration / 2)
        head1_price = None
        head1_date = None
        head2_price = None
        head2_date = None
        neckline_price = None
        neckline_date = None
        # 区间1的最高价格
        start_price = None
        while start_date < mid_date:
            if not head1_date:
                if f"{start_date}日-收盘价" in row:
                    if row[f"{start_date}日-收盘价"] == "-1":
                        start_date = get_n_day(start_date, 1)
                        continue
                    if not start_price:
                        start_price = float(row[f"{start_date}日-收盘价"])
                    head1_price = row[f"{start_date}日-收盘价"]
                    head1_date = start_date
            elif f"{start_date}日-收盘价" in row and row[f"{start_date}日-收盘价"] > head1_price:
                if row[f"{start_date}日-收盘价"] == "-1":
                    start_date = get_n_day(start_date, 1)
                    continue
                head1_price = row[f"{start_date}日-收盘价"]
                head1_date = start_date
            start_date = get_n_day(start_date, 1)
        while end_date > mid_date:
            if not head2_date:
                if f"{mid_date}日-收盘价" in row:
                    if row[f"{mid_date}日-收盘价"] == "-1":
                        mid_date = get_n_day(mid_date, 1)
                        continue
                    head2_price = row[f"{mid_date}日-收盘价"]
                    head2_date = mid_date
            elif f"{mid_date}日-收盘价" in row and row[f"{mid_date}日-收盘价"] > head2_price:
                if row[f"{mid_date}日-收盘价"] == "-1":
                    mid_date = get_n_day(mid_date, 1)
                    continue
                head2_price = row[f"{mid_date}日-收盘价"]
                head2_date = mid_date
            mid_date = get_n_day(mid_date, 1)
        if not head1_date or not head2_date:
            # print(f"{name} head2_date is None or head1_date is None !")
            return "双顶形态判断无效"
        elif (float(head1_price) - start_price) / start_price < 0.05:
            # print(f"{name} left range decrease < 5 or not decrease !")
            return "双顶形态判断无效"
        start_date = get_n_day(head1_date, 1)
        while start_date < head2_date:
            if not neckline_date:
                if row[f"{start_date}日-收盘价"] == "-1":
                    start_date = get_n_day(start_date, 1)
                    continue
                neckline_price = row[f"{start_date}日-收盘价"]
                neckline_date = start_date
            elif f"{start_date}日-收盘价" in row and row[f"{start_date}日-收盘价"] > neckline_price:
                if row[f"{start_date}日-收盘价"] == "-1":
                    start_date = get_n_day(start_date, 1)
                    continue
                neckline_price = row[f"{start_date}日-收盘价"]
                neckline_date = start_date
            start_date = get_n_day(start_date, 1)
        # NOTE 颈线价格要小于两个顶部价格
        if not neckline_price or float(neckline_price) > float(head1_price) or float(neckline_price) > float(
                head2_price):
            # print(f"{name} neckline_price is None or Price increase < 5 !")
            return "双顶形态判断无效"
        elif float(head1_price) <= float(head2_price):
            # print(f"{name} head2_price is low")
            return "双顶形态判断无效"
        now_price = float(row[f"{end_date}日-收盘价"])
        target_price = float(neckline_price) - (float(head1_price) - float(neckline_price))
        min_decrease = target_price - now_price
        if f"{end_date}日-收盘价" not in row:
            print(f"error {name} {end_date}日-收盘价 not sink db !!!")
        if (float(now_price) - float(neckline_price)) / float(neckline_price) > min_diff:
            if float(now_price) > min_decrease + float(neckline_price):
                # print(f"{name}({code}) 已跌破最低跌幅完毕，跳过！！！")
                return "双顶形态跌破有效"
            else:
                print(
                    f"{name}({code}) 双顶形态跌破有效, 最低跌幅 {min_decrease}, 左顶 {head1_date}/{head1_price},"
                    f" 右顶 {head2_date}/{head2_price}, 颈线价格 {neckline_price}, 当前收盘价 {now_price} ")
                return "双顶形态突破有效"
        else:
            print(
                f"{name}({code}) 双顶形态判断有效, 左顶 {head1_date}/{head1_price},"
                f" 右顶 {head2_date}/{head2_price}, 颈线价格 {neckline_price}, 当前收盘价 {now_price} ")
            return "双顶形态判断有效"

    def multi_head_order(self, row, date_list):
        """
        多头：短期均线在长期均线上方
        按均线时间周期的长短，从小到大，从左到右自然排列;
        todo 暂时是月k数据，之后在扩展支持其他时间级别
        """
        # 按时间顺序排列
        # assert start_date < end_date
        # 拉取月k数据
        code = row['_id']
        # 创业板和科创板买不了
        if code[0] == "3" or code[:3] >= "688":
            return ""
        # name = row["2020年度-股票简称"]
        # 计算ma5,ma10,ma20
        df = pd.DataFrame()
        df["close"] = [row[f"{d}月-收盘价"] for d in date_list]
        ma5_ = self.ma(df["close"], 5, self.ma_type_dict["SMA"])
        ma10_ = self.ma(df["close"], 10, self.ma_type_dict["SMA"])
        ma20_ = self.ma(df["close"], 20, self.ma_type_dict["SMA"])
        # print(f"info multi_head_order starting, code {code}, now {now_date_fmt()}")
        # 只计算最后一天的多空，后面可以改成3天多头才算多头这种
        if round(df["close"].values[-1], 3) > round(ma5_.values[-1], 3) > round(ma10_.values[-1], 3) > round(
                ma20_.values[-1], 3):
            return "月线多头排列"
        return ""

    def empty_head_order(self, row, date_list):
        """空头
        移动平均线由大到小自然排列在K线的右上方
        """
        # 拉取月k数据
        code = row['_id']
        # 创业板和科创板买不了
        if code[0] == "3" or code[:3] >= "688":
            return ""
        # name = row["2020年度-股票简称"]
        # 计算ma5,ma10,ma20
        df = pd.DataFrame()
        df["close"] = [row[f"{d}月-收盘价"] for d in date_list]
        ma5_ = self.ma(df["close"], 5, self.ma_type_dict["SMA"])
        ma10_ = self.ma(df["close"], 10, self.ma_type_dict["SMA"])
        ma20_ = self.ma(df["close"], 20, self.ma_type_dict["SMA"])
        # print(f"info multi_head_order starting, code {code}, now {now_date_fmt()}")
        # 只计算最后一天的多空，后面可以改成3天多头才算多头这种
        if round(df["close"].values[-1], 3) < round(ma5_.values[-1], 3) < round(ma10_.values[-1], 3) < round(
                ma20_.values[-1], 3):
            return "月线空头排列"
        return ""


if __name__ == "__main__":
    mt = MyTalib()
    mongo_db = MongoDB(uri="mongodb://127.0.0.1:27017", database="stock")
    # df = mongo_db.fetch_data("stock", {}, ["20210709日"])

    # for k, v in mt.ma_type_dict.items():
    #     print(k)
    #     print(mt.ma(df, 20, v))
    # print(mt.kdj(df))
    # print(mt.ad(df).tail())
    # print(mt.adosc(df).tail())
    # print(mt.obv(df).tail())

    # column_list = ["2020年度-股票简称", "_id"]
    # open_cols, high_cols, low_cols, close_cols = [], [], [], []
    # for i in range(-90, 1):
    #     date = get_n_day(now_date_fmt_8(), i)
    #     for p in ["开盘价", "最高价", "最低价", "收盘价"]:
    #         column_list.append(f"{date}日-{p}")
    #     open_cols.append(f"{date}日-开盘价")
    #     high_cols.append(f"{date}日-最高价")
    #     low_cols.append(f"{date}日-最低价")
    #     close_cols.append(f"{date}日-收盘价")
    # df = mongo_db.fetch_data("stock", {}, column_list)
    # column_list.remove("2020年度-股票简称")
    # column_list.remove("_id")

    # print("形态分析:")
    # print("30日双重底分析:")
    # df.apply(lambda x: mt.w_bottom(x, get_n_day(now_date_fmt_8(), -30), now_date_fmt_8()), axis=1)
    # print("60日双重底分析:")
    # df.apply(lambda x: mt.w_bottom(x, get_n_day(now_date_fmt_8(), -60), now_date_fmt_8()), axis=1)
    # print("90日双重底分析:")
    # df.apply(lambda x: mt.w_bottom(x, get_n_day(now_date_fmt_8(), -90), now_date_fmt_8()), axis=1)
    #
    # print("30日双重顶分析:")
    # df.apply(lambda x: mt.m_head(x, get_n_day(now_date_fmt_8(), -30), now_date_fmt_8()), axis=1)
    # print("60日双重顶分析:")
    # df.apply(lambda x: mt.m_head(x, get_n_day(now_date_fmt_8(), -60), now_date_fmt_8()), axis=1)
    # print("90日双重顶分析:")
    # df.apply(lambda x: mt.m_head(x, get_n_day(now_date_fmt_8(), -90), now_date_fmt_8()), axis=1)

    # print("k线及k线组合分析:")
    # df = mt.history_k_pattern_buy_signal(df, open_cols, high_cols, low_cols, close_cols)
    # df = df[df["买入信号"] != "无"]
    # df.to_csv(PROJ_HOME + "/data/技术面选股/技术面选股.csv", index=False)

    # start_date = "%s0630" % last_n_year(2, mode="str", format="YYYY")
    # end_date = f"{now_year()}0630"
    # trade_cal_df = pd.read_csv(TRADE_CAL_FILE, dtype={"cal_date": str, "is_open": str})
    # columns = ["_id"]
    # date_list = gen_k_date_list(start_date, end_date, trade_cal_df, frequency="m")
    # columns.extend([f"{d}月-收盘价" for d in date_list])
    # data = mongo_db.fetch_data("stock", {}, columns)
    # for d in date_list:
    #     data[f"{d}月-收盘价"] = data[f"{d}月-收盘价"].apply(float)
    # data[f"{end_date}多头"] = data.apply(lambda r: mt.multi_head_order(r, date_list), axis=1)
    # print(data[data[f"{end_date}多头"] != ''].head())
